package com.sirui.jpa.utils;

import cn.hutool.core.util.ReflectUtil;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.query.internal.QueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@CommonsLog
@Component
@Transactional(readOnly = true)
public class SpecificationUtils<T> {
    @Autowired
    private EntityManager entityManager;

    public List executeNativeQuery(String sql, Class clazz) {
        Query nativeQuery = entityManager.createNativeQuery(sql, clazz);
        List resultList = nativeQuery.getResultList();
        return resultList;
    }

    /**
     * 无查询条件
     */
    public Object getComputeResult(Class clazz, String fieldName, CompareType compareType) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Object> query = criteriaBuilder.createQuery();
        Root<Object> root = query.from(clazz);
        if (compareType == CompareType.MAX) {
            query.select(criteriaBuilder.max(root.get(fieldName)));
        } else if (compareType == CompareType.MIN) {
            query.select(criteriaBuilder.min(root.get(fieldName)));
        } else if (compareType == CompareType.SUM) {
            query.select(criteriaBuilder.sum(root.get(fieldName)));
        }
        TypedQuery<Object> typedQuery = entityManager.createQuery(query);
        Object singleResult = typedQuery.getSingleResult();
        return singleResult;
    }

    /**
     * 查询条件 paramBeanList
     */
    public Object getComputeResult(String fieldName, ComputeType computeType, T t, List<ParamBean> paramBeanList) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Object> query = criteriaBuilder.createQuery(Object.class);
        Root<T> root = (Root<T>) query.from(t.getClass());
        if (computeType == ComputeType.COUNT) {
            query.select(criteriaBuilder.count(root.get(fieldName)));
        } else if (computeType == ComputeType.SUM) {
            query.select(criteriaBuilder.sum(root.get(fieldName)));
        } else if (computeType == ComputeType.AVG) {
            query.select(criteriaBuilder.avg(root.get(fieldName)));
        }
        Map<String, List<Predicate>> map = this.getPredicateList(criteriaBuilder, root, t, paramBeanList);
        List<Predicate> andList = map.get("and");
        List<Predicate> orList = map.get("or");
        Predicate preAnd = null;
        Predicate preOr = null;
        Predicate rr = null;
        if (andList != null && andList.size() > 0) {
            preAnd = criteriaBuilder.and(andList.toArray(new Predicate[0]));
        } else {
            //preAnd = criteriaBuilder.and();
        }
        if (orList != null && orList.size() > 0) {
            preOr = criteriaBuilder.or(orList.toArray(new Predicate[0]));
        }
        if (preOr != null) {
            if (preAnd != null) {
                rr = criteriaBuilder.or(preAnd, preOr);
            } else {
                rr = criteriaBuilder.or(preOr);
            }
        } else {
            rr = criteriaBuilder.and(preAnd);
        }
        query.where(rr).getRestriction();
        Object singleResult = entityManager.createQuery(query).getSingleResult();
        return singleResult;
    }

    public Specification<T> getSpecification(T bean, List<ParamBean> paramBeanList) {
        Specification<T> specification = new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> listAnd = new ArrayList<>();
                List<Predicate> listOr = new ArrayList<>();
                Predicate preAnd;
                Predicate preOr;
                for (int i = 0; paramBeanList != null && i < paramBeanList.size(); i++) {
                    ParamBean paramBean = paramBeanList.get(i);
                    String fieldName = paramBean.getFieldName();
                    CompareType compareType = paramBean.getCompareType();
                    RelationType relationType = paramBean.getRelationType();
                    List<Object> fieldValues = paramBean.getFieldValues();
                    if (fieldValues.size() == 0 && bean != null) {
                        fieldValues.add(ReflectUtil.getFieldValue(bean, fieldName));
                    }
                    Predicate predicate = getPredicate(criteriaBuilder, root, compareType, fieldName, fieldValues);
                    switch (relationType) {
                        case AND:
                            listAnd.add(predicate);
                            break;
                        case OR:
                            listOr.add(predicate);
                            break;

                    }
                    log.info(String.format("A@@A1 [{%s},{%s}]", fieldName, fieldValues));
                }
                preAnd = criteriaBuilder.and(listAnd.toArray(new Predicate[0]));
                preOr = criteriaBuilder.or(listOr.toArray(new Predicate[0]));
                Predicate predicate;
                if (listOr.size() > 0) {
                    predicate = criteriaBuilder.or(preAnd, preOr);
                } else {
                    predicate = criteriaBuilder.and(preAnd);
                }
                return predicate;
            }
        };
        return specification;
    }

    private Map<String, List<Predicate>> getPredicateList(CriteriaBuilder criteriaBuilder, Root root, T bean, List<ParamBean> paramBeanList) {
        Map<String, List<Predicate>> map = new LinkedHashMap<>();
        List<Predicate> listAnd = new ArrayList<>();
        List<Predicate> listOr = new ArrayList<>();
        //List<Predicate> list = new ArrayList<>();
        CriteriaQuery<Long> query = criteriaBuilder.createQuery(Long.class);
        for (int i = 0; paramBeanList != null && i < paramBeanList.size(); i++) {
            ParamBean paramBean = paramBeanList.get(i);
            String fieldName = paramBean.getFieldName();
            CompareType compareType = paramBean.getCompareType();
            RelationType relationType = paramBean.getRelationType();
            List<Object> fieldValues = paramBean.getFieldValues();
            if (fieldValues.size() == 0 && bean != null) {
                fieldValues.add(ReflectUtil.getFieldValue(bean, fieldName));
            }
            Predicate predicate = getPredicate(criteriaBuilder, (Root<T>) root, compareType, fieldName, fieldValues);
            switch (relationType) {
                case AND:
                    listAnd.add(predicate);
                    break;
                case OR:
                    listOr.add(predicate);
                    break;

            }
            map.put("and", listAnd);
            map.put("or", listOr);
            //list.add(predicate);
        }
        return map;
    }

    private Predicate getPredicate(CriteriaBuilder criteriaBuilder, Root<T> root, CompareType compareType, String fieldName, List<Object> fieldValues) {
        Predicate predicate = null;
        switch (compareType) {
            case EQ:
                predicate = criteriaBuilder.equal(root.get(fieldName), fieldValues.get(0));
                break;
            case LEFT_LIKE:
                predicate = criteriaBuilder.like(root.get(fieldName), "%" + fieldValues.get(0));
                break;
            case RIGHT_LIKE:
                predicate = criteriaBuilder.like(root.get(fieldName), fieldValues.get(0) + "%");
                break;
            case ALL_LIKE:
                predicate = criteriaBuilder.like(root.get(fieldName), "%" + fieldValues.get(0) + "%");
                break;
            case LT:
                predicate = criteriaBuilder.lessThan(root.get(fieldName), (Comparable) fieldValues.get(0));
                break;
            case LE:
                predicate = criteriaBuilder.lessThanOrEqualTo(root.get(fieldName), (Comparable) fieldValues.get(0));
                break;
            case GT:
                predicate = criteriaBuilder.greaterThan(root.get(fieldName), (Comparable) fieldValues.get(0));
                break;
            case GE:
                predicate = criteriaBuilder.greaterThanOrEqualTo(root.get(fieldName), (Comparable) fieldValues.get(0));
                break;
            case BETWEEN:
                predicate = criteriaBuilder.between(root.get(fieldName), (Comparable) fieldValues.get(0), (Comparable) fieldValues.get(1));
                break;
            case IN:
                Path<Object> path = root.get(fieldName);
                CriteriaBuilder.In<Object> in = criteriaBuilder.in(path);
                for (int i = 0; i < fieldValues.size(); i++) {
                    in.value(fieldValues.get(i));
                }
                predicate = criteriaBuilder.and(in);
                break;
        }
        return predicate;
    }

    public Pageable page(Integer page, Integer size) {
        Pageable pageable = PageRequest.of(page, size);
        return pageable;
    }

    public Pageable page(Integer page, Integer size, Order... orders) {
        Sort sort = Sort.by(orders);
        Pageable pageable = PageRequest.of(page, size, sort);
        return pageable;
    }

    public Order order(String fieldName, Sort.Direction direction) {
        Sort.Order order = new Sort.Order(direction, fieldName);
        return order;
    }

    public ParamBean getParamBean() {
        return new ParamBean();
    }

    public enum ComputeType {
        COUNT("count"), SUM("sum"), AVG("avg");
        private final String desc;

        ComputeType(String desc) {
            this.desc = desc;
        }

        public String getDesc() {
            return desc;
        }
    }

    public enum CompareType {
        EQ("eq"), ALL_LIKE("all_like"), LEFT_LIKE("left_like"), RIGHT_LIKE("right_like"),
        LT("lt"), GT("gt"), LE("le"), GE("ge"), BETWEEN("between"), IN("in"), MAX("max"), MIN("min"), SUM("sum");

        private final String desc;

        CompareType(String desc) {
            this.desc = desc;
        }

        public String getDesc() {
            return desc;
        }
    }

    public enum RelationType {
        AND("and"), OR("or");

        private final String desc;

        RelationType(String desc) {
            this.desc = desc;
        }

        public String getDesc() {
            return desc;
        }
    }

    //----------------------------------------------------------------
    private Query createQuery(String sql, boolean isNativeQuery, Object... params) {
        Query query;
        if (isNativeQuery) {
            query = entityManager.createNativeQuery(sql);
        } else {
            query = entityManager.createQuery(sql);
        }
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                query.setParameter(i + 1, params[i]); // 与Hibernate不同,jpa query从位置1开始
            }
        }
        return query;
    }

    public List queryList(String nativeSql, boolean isNativeQuery, Object... params) {
        Query query = createQuery(nativeSql, isNativeQuery, params);
        if (isNativeQuery) {
            query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.TO_LIST);
        } else {
            query.unwrap(QueryImpl.class).setResultTransformer(Transformers.TO_LIST);
        }
        return query.getResultList();
    }

    public List queryListModel(Class resultClass, String nativeSql, boolean isNativeQuery, Object... params) {
        Query query = createQuery(nativeSql, isNativeQuery, params);
        if (isNativeQuery) {
            query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(resultClass));
        } else {
            query.unwrap(QueryImpl.class).setResultTransformer(Transformers.aliasToBean(resultClass));
        }
        return query.getResultList();
    }

    public List queryListMap(String nativeSql, boolean isNativeQuery, Object... params) {
        Query nativeQuery = createQuery(nativeSql, isNativeQuery, params);
        if (isNativeQuery) {
            nativeQuery.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        } else {
            nativeQuery.unwrap(QueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        }
        return nativeQuery.getResultList();
    }
    //----------------------------------------------------------------

    public Date getDateByStr(String dateStr, String dateFormatType) throws ParseException {
        Date date = DateUtils.parseDate(dateStr, dateFormatType);
        return date;
    }

    public String getDateStrByDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat(DateFormat.DATE_TIME_1.desc);
        String format = sdf.format(date);
        return format;
    }

    public String getDateStrByDate(Date date, String dateFormatType) {
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormatType);
        String format = sdf.format(date);
        return format;
    }

    public enum DateFormat {
        DATE_1("yyyy-MM-dd"), DATE_TIME_1("yyyy-MM-dd HH:mm:ss"), DATE_2("yyyy/MM/dd"), DATE_TIME_2("yyyy/MM/dd HH:mm:ss"), DATE_3("yyyy年MM月dd日"), DATE_TIME_3("yyyy年MM月dd日 HH时mm分ss秒");
        private final String desc;

        DateFormat(String desc) {
            this.desc = desc;
        }

        public String getDesc() {
            return desc;
        }
    }


}
